#include "../include/head.h"
#include <openssl/evp.h>
#include <openssl/err.h>
// 定义进度条宽度
#define PROGRESS_BAR_WIDTH 25
//发送小火车函数
int send_train(socket_t fd_client, int tag, const char *data){
    train_t train;
    bzero(&train,sizeof(train_t));
    train.tag = tag;
    train.data_length = strlen(data);
    memcpy(train.data,data,train.data_length);

    send(fd_client,&train.tag,sizeof(train.tag),0);
    send(fd_client,&train.data_length,sizeof(train.data_length),0);
    send(fd_client,train.data,train.data_length,0);
    return 0;
}

//处理客户端上传文件
int do_puts(socket_t fd_client, MYSQL *mysql, char *cur_path)
{
    train_t train;
    int ret = -1;
    int user_id = 0;
    int parent_id = 0;
    off_t file_size = 0;
    off_t transferred_file_size = 0;
    char filename[256] = {0};//用于接收客户端发来的file1
    char temp_filename[256] = {0};//用于存储 file1.filepart 只在文件表中使用
    char real_filename[512] = {0};//用于存储真实目录下的绝对路径 用于存储 真实空间+hash.filepart, 也就是 ROOTDIR + file_hash.filepart
    char transferred_filename[512] = {0};//用于存储传输完成后，以hash值存储的正确的文件绝对路径，也就是 ROOTDIR + file_hash
    char file_hash[256] = {0};
    char file_path[256] = {0};//用于存储/fu/dir1/file1
    char temp_path[256] = {0};//用于存储/fu/dir1/file1.filepart
    char command[512] = {0};
    MYSQL_RES *result;
    MYSQL_ROW row;

    //接收在服务端新创建的文件名
    bzero(&train, sizeof(train_t));
    recv(fd_client, &train.data_length, sizeof(int), MSG_WAITALL);
    recv(fd_client, train.data, train.data_length, MSG_WAITALL);
    //存取文件名
    strncpy(filename, train.data, sizeof(filename) - 1);

    //接收客户端要发送文件内容的hash值
    bzero(&train, sizeof(train_t));
    recv(fd_client, &train.data_length, sizeof(int), MSG_WAITALL);
    recv(fd_client, train.data, train.data_length, MSG_WAITALL);
    //存取文件内容的hash值
    strncpy(file_hash, train.data, sizeof(file_hash) - 1);

    //接收客户端要发送的文件大小
    bzero(&train, sizeof(train_t));
    recv(fd_client, &train.data_length, sizeof(int), MSG_WAITALL);
    recv(fd_client, &file_size, train.data_length, MSG_WAITALL);

    printf("客户端上传文件：%s\n", filename);
    printf("文件内容的hash值：%s\n", file_hash);
    printf("文件内容的文件大小：%lld\n", (long long)file_size);
    //到这里已经有 cur_path，filename，file_hash，file_size
    //构建完整的虚拟文件路径
    snprintf(file_path, sizeof(file_path), "%s/%s", cur_path, filename);
    snprintf(temp_path, sizeof(temp_path), "%s/%s.filepart", cur_path, filename);
    snprintf(temp_filename, sizeof(temp_filename), "%s.filepart", filename);
    snprintf(real_filename, sizeof(real_filename), "%s/%s.filepart", ROOTDIR, file_hash);
    snprintf(transferred_filename, sizeof(transferred_filename), "%s/%s", ROOTDIR, file_hash);


    //在数据库表中查询是否已经有同名文件
    sprintf(command, "select file_hash, file_size from file where file_path = '%s' and is_deleted = 0;", file_path);                                             
    ret = mysql_query(mysql, command);
    if(ret != 0){
        fprintf(stderr, "%s:%s\n", "mysql_query_getInfo_user", mysql_error(mysql));
        return -1;
    }
    result = mysql_store_result(mysql);
    if (result == NULL || mysql_num_rows(result) == 0) {
        // 同名文件不存在
        printf("不存在同名文件\n");
    } else {
        // 同名文件存在
        row = mysql_fetch_row(result);
        //文件存在，检查哈希值
        if(strcmp(row[0], file_hash) == 0){
            //哈希值匹配，实现极速秒传,原理是发送完整的transferred_file_size回去
            transferred_file_size = atoll(row[1]); 
            bzero(&train, sizeof(train_t));
            train.data_length = sizeof(off_t);
            send(fd_client, &train.data_length, sizeof(int), 0);
            send(fd_client, &transferred_file_size, train.data_length, 0);
            mysql_free_result(result);
            //不需要执行接收操作直接退出
            printf("文件：%s\n极速秒传成功!\n", filename);
            return 0;
        } else {
            //哈希值不匹配，说明客户端上传不同文件，但是文件名已经被占用
            //返回文件大小为-1,即为报错信息，不允许上传该文件
            bzero(&train, sizeof(train_t));
            transferred_file_size = -1;
            train.data_length = sizeof(off_t);
            send(fd_client, &train.data_length, sizeof(int), 0);
            send(fd_client, &transferred_file_size, train.data_length, 0);
            mysql_free_result(result);
            printf("同名文件已存在，%s该文件名不可用\n", filename);
            return -1;
        }
        if(result) mysql_free_result(result);
    }
    //表中不存在 file1文件
    //检查有没有file1.filepart文件
    bzero(command, sizeof(command));
    sprintf(command, "select file_hash, file_size from file where file_path = '%s' and is_deleted = 0;", temp_path);
    ret = mysql_query(mysql, command);
    if(ret != 0){
        fprintf(stderr, "%s:%s\n", "mysql_query_getInfo_user", mysql_error(mysql));
        return -1;
    }
    result = mysql_store_result(mysql);
    if (result == NULL || mysql_num_rows(result) == 0) {
        // 文件不存在
        printf("不存在.filepart文件\n");
        //证明表中也不存在file1.filepart,是一个新的文件
        // 对比新文件的哈希值是否已存在，触发秒传

        //返回文件大小为0，让客户端从头开始上传文件
        bzero(&train, sizeof(train_t));
        transferred_file_size = 0;
        train.data_length = sizeof(off_t);
        send(fd_client, &train.data_length, sizeof(int), 0);
        send(fd_client, &transferred_file_size, train.data_length, 0);
        //首先从数据库中获取当前的user_id 和 file_id，用于新文件的 user_id 和 parent_id
        bzero(command, sizeof(command));
        sprintf(command, "select file_id,user_id from file where file_path = '%s';", cur_path);
        ret = mysql_query(mysql, command);
        if(ret != 0){
            //查询失败
            fprintf(stderr, "查找新创建文件的user_id和parent_id出错: %s\n", mysql_error(mysql));
        }
        result = mysql_store_result(mysql);
        row = mysql_fetch_row(result);
        parent_id = atoi(row[0]);
        user_id = atoi(row[1]);
        //在数据库表中插入一条表项
        bzero(command, sizeof(command));
        sprintf(command, "insert into file (filename,user_id,parent_id,file_path,file_type,file_hash)"
                " values('%s',%d,%d,'%s','%c','%s');", temp_filename, user_id, parent_id, temp_path, '-', file_hash);
        //语句什么时候执行?
        //然后开始接收文件
        ret = recv_file_train(fd_client, mysql, file_hash, transferred_filename, real_filename, file_size, transferred_file_size);

    } else {
        // 同名文件存在
        //证明有.filepart文件，返回当前已写入的文件大小
        result = mysql_store_result(mysql);
        row = mysql_fetch_row(result);
        transferred_file_size = atoll(row[1]);
        bzero(&train, sizeof(train_t));
        train.data_length = sizeof(off_t);
        send(fd_client, &train.data_length, sizeof(int), 0);
        send(fd_client, &transferred_file_size, train.data_length, 0);
        mysql_free_result(result);
        //拼接好文件当下真实的文件名
        ret = recv_file_train(fd_client, mysql, file_hash, transferred_filename, real_filename, file_size, transferred_file_size);
    }
    //判断接收结果
    if (-2 == ret) {
        //客户端异常断开，返回-2
        return -2;
    }
    else if (0 == ret) {
        //正常传输完毕
        printf("transfile finish.\n");
        //修改数据库表中的filename， file_size，file_path
        mysql_free_result(result);
        bzero(command, sizeof(command));
        // 不能用哈希值搜索，要用 temp_filename
        sprintf(command, "update file set filename = '%s',file_path = '%s',file_size = %lld where file_hash = '%s';",
                filename, file_path, (long long)file_size, file_hash);
        ret = mysql_query(mysql, command);
        if(ret != 0){
            //文件传输完毕后， 去掉名字后.filepart，还有路径后.filepart出错；
            bzero(&train, sizeof(train_t));
            printf("文件传输完毕后， 去掉名字后.filepart，还有路径后.filepart出错>_<\n");
            train.tag = TAG_ERROR;
            sprintf(train.data, "文件传输完毕后， 去掉名字后.filepart，还有路径后.filepart出错>_<");
            train.data_length = strlen(train.data) + 1;
            send(fd_client, &train.tag, sizeof(int), 0);
            send(fd_client, &train.data_length, sizeof(int), 0);
            send(fd_client, train.data, train.data_length, 0);
            return -1;
        } else {
            // 改名成功
            bzero(&train, sizeof(train_t));
            //回应客户端
            train.tag = TAG_RESPONSEPUTS;
            sprintf(train.data, "文件[%s]上传完毕!!!", filename);
            train.data_length = strlen(train.data);
            send(fd_client, &train.tag, sizeof(int), 0);
            send(fd_client, &train.data_length, sizeof(int), 0);
            send(fd_client, train.data, train.data_length, 0);
            return 0;
        }
    }
    return 0;
}

// 获取文件大小函数
int get_filesize(char *filename) {
    struct stat st;
    if (stat(filename, &st) == 0) {
        return (int)st.st_size;
    }
    return -1;
}

// 显示进度条的函数
void show_progress(off_t current, off_t total) {
    if (total <= 0) return;  // 避免除以零

    double percentage = (double)current / total;
    int pos = (int)(percentage * PROGRESS_BAR_WIDTH);

    printf("[");
    for (int i = 0; i < PROGRESS_BAR_WIDTH; ++i) {
        if (i < pos) printf("=");
        else if (i == pos) printf(">");
        else printf(" ");
    }
    printf("] %.2f%%\r", percentage * 100);
    /* printf("] %.2f%% (%ld/%ld bytes)", percentage * 100, current, total); */
    fflush(stdout);
}
// 计算文件内容hash值的函数
int calculate_file_hash(const char *filename, char *file_hash) {
    FILE *file = fopen(filename, "rb");
    if (!file) {
        printf("错误: 无法打开文件 '%s' 计算哈希值\n", filename);
        return -1;  // 返回错误码
    }

    EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
    if (!mdctx) {
        fclose(file);
        return -1;
    }

    // 使用现代API初始化SHA512
    if (EVP_DigestInit_ex(mdctx, EVP_sha512(), NULL) != 1) {
        EVP_MD_CTX_free(mdctx);
        fclose(file);
        return -1;
    }

    unsigned char buffer[1024];
    size_t bytesRead;
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) != 0) {
        if (EVP_DigestUpdate(mdctx, buffer, bytesRead) != 1) {
            EVP_MD_CTX_free(mdctx);
            fclose(file);
            return -1;
        }
    }

    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int hash_length;
    if (EVP_DigestFinal_ex(mdctx, hash, &hash_length) != 1) {
        EVP_MD_CTX_free(mdctx);
        fclose(file);
        return -1;
    }

    EVP_MD_CTX_free(mdctx);
    fclose(file);

    // 将哈希值转换为十六进制字符串并存入file_hash
    for (unsigned int i = 0; i < hash_length; i++) {
        sprintf(file_hash + (i * 2), "%02x", hash[i]);
    }
    file_hash[hash_length * 2] = '\0';

    return 0;  // 成功返回0
}
// 接收文件
int recv_file_train(socket_t fd_client, MYSQL *mysql, const char* file_hash, const char *transferred_filename, char *real_filename, off_t file_size, off_t transferred_file_size)
{
    train_t train;
    int ret = -1;
    char transferred_hash[256] = {0};
    char command[512] = {0};
    MYSQL_RES *result;
    //断点续传
    //打开文件，并偏移文件指针
    int fd_file = open(real_filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);//打开或创建 真实的文件
    ERROR_CHECK(fd_file, -1, "open recv_file_train文件打开或文件创建失败");
    lseek(fd_file, transferred_file_size, SEEK_SET);

    off_t total_size = file_size;
    off_t current_size = transferred_file_size;
    off_t last_size = current_size;
    off_t one_percent_of_progress = file_size / 100;

    //循环接收文件内容
    while (1) {
        //重置小货车
        memset(&train, 0, sizeof(train));
        //先接数据长度
        ret = recv(fd_client, &train.data_length, sizeof(int), MSG_WAITALL);
        if (0 == ret) {
            //对端异常断开
            close(fd_file);
            //需要将数据库表中当前写入的内容大小更新
            bzero(command, sizeof(command));
            sprintf(command, "update file set file_size = %lld where file_hash = '%s';",(long long)current_size, file_hash);
            ret = mysql_query(mysql, command);
            if(ret != 0){
                fprintf(stderr, "修改已经传输的文件大小错误：%s\n", mysql_error(mysql));
            }
            return -2;
        }
        if (train.data_length == 0 && current_size == total_size) {
            //传输完毕
            printf("[========================>] 100.00%%\n");
            //将传输完成的文件内容计算hash值进行验证
            //这里传入的参数是文件的绝对路径和 传入传出参数保存刚刚传输内容的hash值
            ret = calculate_file_hash(real_filename, transferred_hash);
            if(strcmp(file_hash, transferred_hash) == 0){
                //证明传输的文件内容正确
                //关闭文件
                close(fd_file);
                //修改文件名，去掉.filepart的后缀
                ret = rename(real_filename, transferred_filename);
                if(ret == -1){
                    perror("rename failed");
                    return -1;
                }
                //注意别忘了在do_puts函数接收到recv_file_train函数的返回值之后更新数据表中内容文件名和文件路径
                return 0;
            }else{
                //文件传输过程中出现错误
                //首先删除真实目录中文件，并且修改数据库中is_deleted = 1
                ret = remove(real_filename);
                if(ret != 0){
                    perror("删除文件失败");
                    return -1;
                }
                printf("传输内容有误，文件删除成功");
                //修改数据库表中内容
                bzero(command, sizeof(command));
                // 这里也不能用哈希值搜索
                sprintf(command, "update file set is_deleted = 1 where file_hash = '%s';", file_hash);
                ret = mysql_query(mysql, command);
                if(ret != 0){
                    fprintf(stderr, "删除传输错误的文件表项失败：%s\n", mysql_error(mysql));
                }
                result = mysql_store_result(mysql);
                //返回传输文件内容错误的错误信息
                bzero(&train, sizeof(train_t));
                train.tag = TAG_ERROR;
                sprintf(train.data, "文件传输内容错误>_<\n");
                train.data_length = sizeof(train.data);
                send(fd_client, &train.tag, sizeof(int), 0);
                send(fd_client, &train.data_length, sizeof(int), 0);
                send(fd_client, train.data, train.data_length, 0);
                mysql_free_result(result);
                return -1;
            }
        }

        //根据数据长度接收数据内容
        //防止发送方发的慢，导致接收缓冲区将车厢当成车头，设置recv参数为MSG_WAITALL
        ret = recv(fd_client, train.data, train.data_length, MSG_WAITALL);
        if (0 == ret) {
            //对端异常断开
            close(fd_file);
            //需要将数据库表中当前写入的内容大小更新
            bzero(command, sizeof(command));
            sprintf(command, "update file set file_size = %lld where file_hash = '%s';",(long long)current_size, file_hash);
            ret = mysql_query(mysql, command);
            if(ret != 0){
                fprintf(stderr, "修改已经传输的文件大小错误：%s\n", mysql_error(mysql));
            }
            return -2;
        }

        //将接收数据写入文件
        write(fd_file, train.data, ret);
        current_size += ret;
        // 更新当前大小并显示进度
        if (current_size - last_size >= one_percent_of_progress) {
            show_progress(current_size, total_size);
            last_size = current_size;
        }
    }
}
//处理客户端的文件下载请求
int do_gets(socket_t fd_client, MYSQL *mysql, char *cur_path)
{
    train_t train;
    bzero(&train,sizeof(train_t));
    //客户端是下载，服务器是发送
    //文件名是命令的第二个参数
    char temp_path[256] = {0};//临时路径
    char file_hash[256] = {0};//文件hash值
    char command[1024] = {0};//mysql语句
    char errmsg[256] = {0};//错误信息
    off_t file_size = 0;//文件总大小
    char filename[256] = {0};//文件名
    //接收文件名长度
    recv(fd_client,&file_size,sizeof(file_size),0);
    //接收文件名
    recv(fd_client,filename,file_size,MSG_WAITALL);
    strcpy(temp_path, ROOTDIR);
    strcat(temp_path,"/");
    strcat(temp_path,filename);//temp_path = "/home/fhf/user_space/filename"
    sprintf(command,"select file_size,file_hash from file where file_path = '%s' and is_deleted = 0 and file_type = '-';",temp_path);
    if(mysql_query(mysql,command)){
        fprintf(stderr,"%s:%s\n","gets file_hash",mysql_error(mysql));
        strcpy(errmsg,"查询失败@_@\n");
        send_train(fd_client,TAG_ERROR,errmsg);
        return -1;
    }

    MYSQL_RES *result = mysql_store_result(mysql);
    if(mysql_num_rows(result) == 0){
        strcpy(errmsg,"文件不存在T_T\n");
        send_train(fd_client,TAG_ERROR,errmsg);
        return -1;
    }
    // 给客户端发送确认文件存在的信息

    //得到文件大小和hash值
    MYSQL_ROW row = mysql_fetch_row(result);
    file_size = atoi(row[0]);
    strcpy(file_hash,row[1]);

    //发送文件hash值
    train.data_length = strlen(file_hash);
    memcpy(train.data,file_hash,train.data_length);
    send(fd_client,&train.data_length,sizeof(train.data_length),0);
    send(fd_client,train.data,train.data_length,0);
    //获取文件的真实路径
    bzero(temp_path,sizeof(temp_path));
    char backup_cur[1024] = {0};//cur_path副本
    char *p = strtok(backup_cur,"/");
    strcpy(temp_path,ROOTDIR);
    strcat(temp_path,p);
    strcat(temp_path,"/");
    strcat(temp_path,file_hash);
    //发送文件长度
    train.data_length = sizeof(off_t);
    send(fd_client,&train.data_length,sizeof(int),0);
    send(fd_client,&file_size,train.data_length,0);
    //判断用哪种方式发送给客户端
    if(file_size > HUNDRED_MEGABYTE){
        send_file_sendfile(fd_client,temp_path,file_size);
    }
    else{
        send_file_train(fd_client,temp_path);
    }
    return 0;
}
//用普通方式传输文件 file_size < 100M
int send_file_train(int fd_socket, char *filename)
{
    //打开传输文件
    int file_fd = open(filename, O_RDWR);
    ERROR_CHECK(file_fd, -1, "open");
    train_t train;	 //定义一个小货车，用来传输文件
    int ret = -1;

    //断点续传
    //从客户端接收文件大小，如果大于0就偏移文件指针
    int offset = 0;
    recv(fd_socket, &offset, sizeof(int), 0);
    if (offset) {
        lseek(file_fd, offset, SEEK_SET);
    }

    //发文件内容
    while (1) {
        memset(train.data, 0, sizeof(train.data));
        train.data_length = read(file_fd, train.data, sizeof(train.data));
        ERROR_CHECK(train.data_length, -1, "read");
        if (0 == train.data_length) {
            //传输完成，通知客户端，然后退出循环
            ret = send(fd_socket, &train.data_length, sizeof(int), 0);
            ERROR_CHECK(ret, -1, "send");
            //关闭传输文件
            close(file_fd);
            return 0;
        }

        //判断客户端异常异常退出
        ret = send(fd_socket, &train.data_length, sizeof(int), 0);
        if (-1 == ret) {
            //客户端异常断开，退出循环
            printf("Client Abnormal Exit!\n");
            //关闭传输文件
            close(file_fd);
            return -2;
        }
        ret = send(fd_socket, train.data, train.data_length, 0);
        if (-1 == ret) {
            //客户端异常断开，退出循环
            printf("Client Abnormal Exit!\n");
            //关闭传输文件
            close(file_fd);
            return -2;
        }
    }
}

//用零拷贝方式传输文件 file_size > 100M
int send_file_sendfile(int fd_socket, char *filename, off_t filesize){
    // 打开传输文件
    int file_fd = open(filename, O_RDWR);
    ERROR_CHECK(file_fd, -1, "open");
    train_t train;   //定义一个小货车，用来传输文件
    int ret = -1;
    // mmap
    char *p = (char*)mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);
    ERROR_CHECK(p, NULL, "mmap");

    //断点续传
    //从接收方获得文件目前大小，如果大于0就偏移文件指针
    int offset = 0;
    recv(fd_socket, &offset, sizeof(int), MSG_WAITALL);
    if (offset) {
        lseek(file_fd, offset, SEEK_SET);
    }

    // 累计发送数据长度
    int send_length = offset;

    //发文件内容
    while (1) {
        memset(&train.data_length, 0, sizeof(train.data_length));
        unsigned long rest = filesize - send_length;
        train.data_length = sizeof(train.data);
        if (rest < sizeof(train.data)) {
            train.data_length = rest;
        }
        if (0 == train.data_length) {
            //传输完成，通知接收方，然后退出循环
            ret = send(fd_socket, &train.data_length, sizeof(int), 0);
            ERROR_CHECK(ret, -1, "send");
            //关闭传输文件
            munmap(p, filesize);
            close(file_fd);
            return 0;
        }

        //判断客户端异常异常退出
        ret = send(fd_socket, &train.data_length, sizeof(int), 0);
        if (-1 == ret) {
            //客户端异常断开，退出循环
            printf("Client Abnormal Exit!\n");
            //关闭传输文件
            munmap(p, filesize);
            close(file_fd);
            return -2;
        }
        ret = send(fd_socket, p /*+ offset*/ + send_length, train.data_length, 0);
        if (-1 == ret) {
            //客户端异常断开，退出循环
            printf("Client Abnormal Exit!\n");
            //关闭传输文件
            munmap(p, filesize);
            close(file_fd);
            return -2;
        }
        send_length += train.data_length;
    }
}
